package com.jxpanda.infrastructure.crypto.algorithm.encrypt.contract;

import com.jxpanda.infrastructure.crypto.constant.AlgorithmEncrypt;
import com.jxpanda.infrastructure.crypto.toolkit.Base64Kit;
import com.jxpanda.infrastructure.crypto.toolkit.KeyLoadKit;
import lombok.AccessLevel;
import lombok.Getter;

import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

@Getter
public class AbstractSymmetricEncrypt implements SymmetricEncrypt {

    private final String algorithm;

    private final IvParameterSpec iv;

    private final SecretKey key;

    public AbstractSymmetricEncrypt(String algorithm, SecretKey key) {
        this.key = key;
        this.iv = null;
        this.algorithm = algorithm;
    }

    public AbstractSymmetricEncrypt(String algorithm, SecretKey key, byte[] ivBytes) {
        this(algorithm, key, new IvParameterSpec(ivBytes));
    }

    public AbstractSymmetricEncrypt(String algorithm, SecretKey key, IvParameterSpec ivParameterSpec) {
        this.key = key;
        this.iv = ivParameterSpec;
        this.algorithm = algorithm;
    }

    @SuppressWarnings("unchecked")
    @Getter(AccessLevel.PROTECTED)
    public static abstract class Builder<S extends AlgorithmEncrypt.Symmetric, E extends AbstractSymmetricEncrypt> {
        private final S symmetricAlgorithm;
        private SecretKey key;
        private byte[] ivBytes;
        private IvParameterSpec ivParameterSpec;

        protected Builder(S symmetricAlgorithm) {
            this.symmetricAlgorithm = symmetricAlgorithm;
        }

        public Builder<S, E> key(SecretKey key) {
            this.key = key;
            return this;
        }

        public Builder<S, E> key(byte[] keyBytes) {
            this.key = KeyLoadKit.loadSecretKey(symmetricAlgorithm, keyBytes);
            return this;
        }

        public Builder<S, E> key(String keyString) {
            this.key = KeyLoadKit.loadSecretKey(symmetricAlgorithm, keyString);
            return this;
        }

        public Builder<S, E> keyBase64(String keyBase64) {
            this.key = KeyLoadKit.loadSecretKeyFromBase64(symmetricAlgorithm, keyBase64);
            return this;
        }


        public Builder<S, E> iv(String ivString) {
            this.ivBytes = ivString.getBytes(StandardCharsets.UTF_8);
            return this;
        }

        public Builder<S, E> ivBase64(String ivBase64) {
            this.ivBytes = Base64Kit.decode(ivBase64);
            return this;
        }

        public Builder<S, E> iv(byte[] ivBytes) {
            this.ivBytes = ivBytes;
            return this;
        }

        public Builder<S, E> iv(IvParameterSpec ivParameterSpec) {
            this.ivParameterSpec = ivParameterSpec;
            return this;
        }

        protected IvParameterSpec getIvParameterSpec() {
            if (ivParameterSpec == null && ivBytes != null) {
                ivParameterSpec = new IvParameterSpec(ivBytes);
            }
            return ivParameterSpec;
        }

        protected SecretKey getKeyOrDefault() {
            return Objects.requireNonNullElse(key, getSymmetricAlgorithm().keyProvider().generateKey());
        }

        protected IvParameterSpec getIvParameterSpecOrDefault(IvParameterSpec defaultIvParameterSpec) {
            return Objects.requireNonNullElse(getIvParameterSpec(), defaultIvParameterSpec);
        }

        public E build() {
            return (E) new AbstractSymmetricEncrypt(symmetricAlgorithm.getAlgorithm(), getKey(), getIvParameterSpec());
        }

        public abstract E buildDefault();

    }

}
